● Zion Boggan
repos/Sightline/frontend/src/app/cameras/[id]/page.tsx
zionboggan.com ↗
51 lines · tsx
History for this file →
1
"use client";
2
import { useEffect, useState } from "react";
3
import { useParams } from "next/navigation";
4
import { api, CameraDetail } from "@/lib/api";
5
 
6
export default function CameraDetailPage() {
7
  const { id } = useParams<{ id: string }>();
8
  const [cam, setCam] = useState<CameraDetail | null>(null);
9
  const [transits, setTransits] = useState<{ from: string; to: string; weight: number }[]>([]);
10
  const [err, setErr] = useState<string | null>(null);
11
 
12
  useEffect(() => {
13
    Promise.all([api.cameraDetail(id), api.cameraTransits(id)])
14
      .then(([c, t]) => { setCam(c); setTransits(t.transitions); })
15
      .catch((e) => setErr(e.status === 401 ? "Sign in to view camera detail." : "Camera not found."));
16
  }, [id]);
17
 
18
  if (err) return <div className="badge danger">{err}</div>;
19
  if (!cam) return <div className="muted">Loading…</div>;
20
 
21
  return (
22
    <div>
23
      <h1>{cam.name || cam.external_camera_id}</h1>
24
      <p className="muted">External ID: {cam.external_camera_id}</p>
25
      <div className="grid">
26
        <div className="card"><div className="muted">Total reads</div><div className="kpi">{cam.read_count}</div></div>
27
        <div className="card"><div className="muted">Facing</div><div>{cam.direction_facing || "-"}</div></div>
28
        <div className="card"><div className="muted">Location</div><div>{cam.lat != null ? `${cam.lat.toFixed(4)}, ${cam.lon!.toFixed(4)}` : "unknown"}</div></div>
29
        <div className="card"><div className="muted">Address</div><div>{cam.address || "-"}</div></div>
30
      </div>
31
 
32
      <h2>Camera-to-camera transits</h2>
33
      <p className="muted">Observed vehicle movements into and out of this camera (directed, weighted).</p>
34
      <div className="card">
35
        <table>
36
          <thead><tr><th>From</th><th>To</th><th>Vehicles</th></tr></thead>
37
          <tbody>
38
            {transits.map((t, i) => (
39
              <tr key={i}>
40
                <td className={t.to === id ? "muted" : ""}>{t.from === id ? "(this camera)" : t.from.slice(0, 8)}</td>
41
                <td className={t.from === id ? "muted" : ""}>{t.to === id ? "(this camera)" : t.to.slice(0, 8)}</td>
42
                <td><span className="badge">{t.weight}</span></td>
43
              </tr>
44
            ))}
45
            {!transits.length && <tr><td colSpan={3} className="muted">No transitions observed.</td></tr>}
46
          </tbody>
47
        </table>
48
      </div>
49
    </div>
50
  );
51
}